package com.galaxis.wms.security;

//import com.galaxis.wms.WhIdHolder;

import com.galaxis.wms.entity.BasUser;
import com.galaxis.wms.entity.SysUserSecurityContext;
import com.galaxis.wms.enums.CEnableStatus;
import com.galaxis.wms.security.utils.UserInfoConvertUtils;
import com.querydsl.core.Tuple;
import com.yvan.core.DbConv;
import com.yvan.core.SystemClock;
import com.yvan.core.mapper.JacksonMapper;
import com.yvan.core.tuples.TupleTwo;
import com.yvan.security.SecurityContextRepository;
import com.yvan.security.SecurityDataSource;
import com.yvan.security.config.RedisConfig;
import com.yvan.security.config.SecurityConfig;
import com.yvan.security.exception.AuthenticationInnerException;
import com.yvan.security.model.LoginContext;
import com.yvan.security.model.SecurityContext;
import com.yvan.security.utils.SecurityContextEqualsUtils;
import com.yvan.security.utils.SecurityRedisKey;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.sql.Timestamp;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;

import static com.galaxis.wms.query.QBasResource.basResource;
import static com.galaxis.wms.query.QBasRole.basRole;
import static com.galaxis.wms.query.QBasRoleResourceRel.basRoleResourceRel;
import static com.galaxis.wms.query.QBasUser.basUser;
import static com.galaxis.wms.query.QBasUserRoleRel.basUserRoleRel;
import static com.galaxis.wms.query.QSysUserSecurityContext.sysUserSecurityContext;

/**
 * 作者：lizw <br/>
 * 创建时间：2020/12/14 21:29 <br/>
 */
@Slf4j
public class DefaultSecurityContextRepository implements SecurityContextRepository {
    @Override
    public void cacheContext(LoginContext context, SecurityConfig securityConfig, HttpServletRequest request, HttpServletResponse response) {
        final RedisConfig redisConfig = securityConfig.getRedis();
        final Long userId = DbConv.asLong(context.getUserInfo().getUserId());
        SecurityContext securityContext = SecurityDataSource.getJdbc().beginTX(status -> {
            // 锁住用户行(等待SecurityContext更新完毕)
            SecurityDataSource.getQueryDSL().select(basUser.userId).from(basUser).where(basUser.userId.eq(userId)).forUpdate().fetchOne();
            // 获取最新的 SecurityContext
            TupleTwo<SysUserSecurityContext, SecurityContext> tupleTwo = newSecurityContext( userId);
            if (tupleTwo == null) {
                SecurityDataSource.getQueryDSL().delete(sysUserSecurityContext)
                        .where(sysUserSecurityContext.userId.eq(userId))
                        .execute();
                return null;
            }
            SysUserSecurityContext userSecurityContext =
                    SecurityDataSource.getQueryDSL().selectFrom(sysUserSecurityContext)
                            .where(sysUserSecurityContext.userId.eq(userId))
                            .fetchOne();
            // 不存在直接新增
            if (userSecurityContext == null) {
                SecurityDataSource.getQueryDSL().insert(sysUserSecurityContext).populate(tupleTwo.getValue1()).execute();
                return tupleTwo.getValue2();
            }
            SecurityContext sc = JacksonMapper.getInstance().fromJson(userSecurityContext.getSecurityContext(), SecurityContext.class);
            // 存在则比对刷新
            if (SecurityContextEqualsUtils.equals(sc, tupleTwo.getValue2())) {
                return tupleTwo.getValue2();
            }
            SecurityDataSource.getQueryDSL().update(sysUserSecurityContext)
                    .set(sysUserSecurityContext.securityContext, JacksonMapper.getInstance().toJson(tupleTwo.getValue2()))
                    .set(sysUserSecurityContext.updateAt, new Timestamp(SystemClock.now()))
                    .where(sysUserSecurityContext.id.eq(userSecurityContext.getId()))
                    .execute();
            return tupleTwo.getValue2();
        });
        // SecurityContext写入Redis缓存
        if (redisConfig.isEnable() && securityContext != null) {
            SecurityDataSource.getRedis().vSet(
                    SecurityRedisKey.getSecurityContextKey(redisConfig.getRedisNamespace(), 0L, DbConv.asString(userId)),
                    securityContext
            );
        }
        log.debug("### 缓存SecurityConfig成功 -> userId={}", userId);
    }

    @Override
    public SecurityContext loadContext(String userId, Claims claims, SecurityConfig securityConfig, HttpServletRequest request, HttpServletResponse response) {
        final RedisConfig redisConfig = securityConfig.getRedis();
        SecurityContext securityContext = null;
        // 从Redis缓存中读SecurityContext
        if (redisConfig.isEnable()) {
            securityContext = SecurityDataSource.getRedis().vGet(
                    SecurityRedisKey.getSecurityContextKey(redisConfig.getRedisNamespace(), 0L, userId),
                    SecurityContext.class
            );
        }
        if (securityContext == null) {
            // 从数据库中读SecurityContext
            securityContext = SecurityDataSource.getJdbc().beginTX(status -> {
                // 锁住用户行(等待SecurityContext更新完毕)
                SecurityDataSource.getQueryDSL().select(basUser.userId).from(basUser).where(basUser.userId.eq(DbConv.asLong(userId))).forUpdate().fetchOne();
                // 开始加载SecurityContext
                SysUserSecurityContext userSecurityContext = SecurityDataSource.getQueryDSL().selectFrom(sysUserSecurityContext)
                        .where(sysUserSecurityContext.userId.eq(DbConv.asLong(userId)))
                        .fetchOne();
                if (userSecurityContext == null) {
                    TupleTwo<SysUserSecurityContext, SecurityContext> tupleTwo = newSecurityContext(DbConv.asLong(userId));
                    if (tupleTwo == null) {
                        return null;
                    }
                    SecurityDataSource.getQueryDSL().insert(sysUserSecurityContext).populate(tupleTwo.getValue1()).execute();
                    return tupleTwo.getValue2();
                }
                return JacksonMapper.getInstance().fromJson(userSecurityContext.getSecurityContext(), SecurityContext.class);
            });
            // SecurityContext写入Redis缓存
            if (redisConfig.isEnable() && securityContext != null) {
                SecurityDataSource.getRedis().vSet(
                        SecurityRedisKey.getSecurityContextKey(redisConfig.getRedisNamespace(), 0L, userId),
                        securityContext
                );
            }
        }
        if (securityContext == null) {
            throw new AuthenticationInnerException("加载SecurityConfig失败");
        }
        log.debug("### 加载SecurityConfig成功 -> userId={}", userId);
        return securityContext;
    }

    /**
     * 刷新 SecurityContext
     *
     * @param userId 用户id
     */
    protected TupleTwo<SysUserSecurityContext, SecurityContext> newSecurityContext(Long userId) {
        BasUser user = SecurityDataSource.getQueryDSL().select(basUser)
                .from(basUser)
                .where(basUser.userId.eq(userId))
                .fetchOne();
        if (user == null) {
            return null;
        }
        // 查询角色
        List<Tuple> roles = SecurityDataSource.getQueryDSL().select(basRole.roleCode, basRole.roleId)
                .from(basUserRoleRel).leftJoin(basRole).on(basUserRoleRel.roleId.eq(basRole.roleId))
                .where(basRole.isEnable.eq(CEnableStatus.ENABLE))
                .where(basUserRoleRel.userId.eq(userId))
                .fetch();
        // 查询权限
        List<String> rolePermissions = SecurityDataSource.getQueryDSL().select(basResource.permissionCode).distinct()
                .from(basResource).leftJoin(basRoleResourceRel).on(basResource.resourceId.eq(basRoleResourceRel.resourceId))
                .where(basResource.permissionCode.isNotNull())
                .where(basResource.isEnable.eq(CEnableStatus.ENABLE))
                .where(basRoleResourceRel.roleId.in(roles.stream().map(item -> item.get(basRole.roleId)).collect(Collectors.toSet())))
                .fetch();
        // SecurityContext
        SecurityContext securityContext = new SecurityContext(
                UserInfoConvertUtils.convertToUserInfo(user),
                roles.stream().map(item -> item.get(basRole.roleCode)).map(String::valueOf).collect(Collectors.toSet()),
                new HashSet<>(rolePermissions)
        );
        // SysUserSecurityContext
        SysUserSecurityContext userSecurityContext = new SysUserSecurityContext();
        // userSecurityContext.setId(SnowFlake.SNOW_FLAKE.nextId());
        userSecurityContext.setId(SecurityDataSource.getDao().snowId(sysUserSecurityContext.getTableName()));
        userSecurityContext.setUserId(DbConv.asLong(userId));
        userSecurityContext.setSecurityContext(JacksonMapper.getInstance().toJson(securityContext));
        userSecurityContext.setCreateAt(new Timestamp(SystemClock.now()));
        return TupleTwo.creat(userSecurityContext, securityContext);
    }
}
